/**
 * jquery-autocompleter v0.4.0-0 - 2020-01-04
 * Easy customisable and with localStorage cache support.
 * http://github.com/fitiskin/jquery-autocompleter
 *
 * @license (c) 2020 Artem Fitiskin MIT Licensed
 */

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
        typeof define === 'function' && define.amd ? define(['jquery'], factory) :
            (global = global || self, factory(global.$));
}(this, (function ($) { 'use strict';

    $ = $ && $.hasOwnProperty('default') ? $['default'] : $;

    function _typeof(obj) {
        if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
            _typeof = function (obj) {
                return typeof obj;
            };
        } else {
            _typeof = function (obj) {
                return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
            };
        }

        return _typeof(obj);
    }

    var guid = 0,
        ignoredKeyCode = [9, 13, 17, 19, 20, 27, 33, 34, 35, 36, 37, 39, 44, 92, 113, 114, 115, 118, 119, 120, 122, 123, 144, 145],
        allowOptions = ["source", "empty", "limit", "cache", "cacheExpires", "focusOpen", "selectFirst", "changeWhenSelect", "highlightMatches", "ignoredKeyCode", "customLabel", "customValue", "template", "offset", "combine", "callback", "minLength", "delay"],
        userAgent = window.navigator.userAgent || window.navigator.vendor || window.opera,
        isFirefox = /Firefox/i.test(userAgent),
        isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent),
        isFirefoxMobile = isFirefox && isMobile,
        $body = null,
        delayTimeout = null,
        localStorageKey = "autocompleterCache",
        supportLocalStorage = function () {
            var supported = typeof window.localStorage !== "undefined";

            if (supported) {
                try {
                    localStorage.setItem("autocompleter", "autocompleter");
                    localStorage.removeItem("autocompleter");
                } catch (e) {
                    supported = false;
                }
            }

            return supported;
        }();
    /**
     * Don't forget update README.md
     *
     * @options
     * @param source [(string|object)] <null> "URL to the server or a local object"
     * @param asLocal [boolean] <false> "Parse remote response as local source"
     * @param empty [boolean] <true> "Launch if value is empty"
     * @param limit [int] <10> "Number of results to be displayed"
     * @param minLength [int] <0> "Minimum length for autocompleter"
     * @param delay [int] <0> "Few milliseconds to defer the request"
     * @param customClass [array] <[]> "Array with custom classes for autocompleter element"
     * @param cache [boolean] <true> "Save xhr data to localStorage to avoid the repetition of requests"
     * @param cacheExpires [int] <86400> "localStorage data lifetime"
     * @param focusOpen [boolean] <true> "Launch autocompleter when input gets focus"
     * @param hint [boolean] <false> "Add hint to input with first matched label, correct styles should be installed"
     * @param selectFirst [boolean] <false> "If set to true, first element in autocomplete list will be selected automatically, ignore if changeWhenSelect is on"
     * @param changeWhenSelect [boolean] <true> "Allows to change input value using arrow keys navigation in autocomplete list"
     * @param highlightMatches [boolean] <false> "This option defines <strong> tag wrap for matches in autocomplete results"
     * @param ignoredKeyCode [array] <[]> "Array with ignorable keycodes"
     * @param customLabel [boolean] <false> "The name of object's property which will be used as a label"
     * @param customValue [boolean] <false> "The name of object's property which will be used as a value"
     * @param template [(string|boolean)] <false> "Custom template for list items"
     * @param offset [(string|boolean)] <false> "Source response offset, for example: response.items.posts"
     * @param combine [function] <null> "Returns an object with ajax data. Useful if you want to pass some additional server options or replace it at all"
     * @param callback [function] <$.noop> "Select value callback function. Arguments: value, index"
     */


    var options = {
        source: null,
        method:'post',
        subKey:'query',
        formatData:null,
        asLocal: false,
        empty: true,
        limit: 20,
        minLength: 0,
        delay: 0,
        customClass: [],
        cache: true,
        cacheExpires: 3600,
        focusOpen: true,
        hint: false,
        selectFirst: false,
        changeWhenSelect: true,
        highlightMatches: false,
        ignoredKeyCode: [],
        customLabel: false,
        customValue: false,
        template: false,
        offset: false,
        combine: null,
        callback: $.noop
    };
    var publics = {
        /**
         * @method
         * @name defaults
         * @description Sets default plugin options
         * @param opts [object] <{}> "Options object"
         * @example $.autocompleter('defaults', opts);
         */
        defaults: function defaults(opts) {
            options = $.extend(options, opts || {});
            return _typeof(this) === "object" ? $(this) : true;
        },

        /**
         * @method
         * @name option
         * @description Open autocompleter list
         */
        option: function option(properties) {
            return $(this).each(function (i, input) {
                var data = $(input).next(".autocompleter").data("autocompleter");

                for (var property in properties) {
                    if ($.inArray(property, allowOptions) !== -1) {
                        data[property] = properties[property];
                    }
                }
            });
        },

        /**
         * @method
         * @name open
         * @description Open autocompleter list
         */
        open: function open() {
            return $(this).each(function (i, input) {
                var data = $(input).next(".autocompleter").data("autocompleter");

                if (data) {
                    _open(null, data);
                }
            });
        },

        /**
         * @method
         * @name close
         * @description Close autocompleter list
         */
        close: function close() {
            return $(this).each(function (i, input) {
                var data = $(input).next(".autocompleter").data("autocompleter");

                if (data) {
                    _close(null, data);
                }
            });
        },

        /**
         * @method
         * @name clearCache
         * @description Remove localStorage cache
         */
        clearCache: function clearCache() {
            _deleteCache();
        },

        /**
         * @method
         * @name destroy
         * @description Removes instance of plugin
         * @example $('.target').autocompleter('destroy');
         */
        destroy: function destroy() {
            return $(this).each(function (i, input) {
                var data = $(input).next(".autocompleter").data("autocompleter");

                if (data) {
                    // Abort xhr
                    if (data.jqxhr) {
                        data.jqxhr.abort();
                    } // If has selected item & open - confirm it


                    if (data.$autocompleter.hasClass("open")) {
                        data.$autocompleter.find(".autocompleter-selected").trigger("click.autocompleter");
                    } // Restore original autocomplete attr


                    if (!data.originalAutocomplete) {
                        data.$node.removeAttr("autocomplete");
                    } else {
                        data.$node.attr("autocomplete", data.originalAutocomplete);
                    } // Remove autocompleter & unbind events


                    data.$node.off(".autocompleter").removeClass("autocompleter-node");
                    data.$autocompleter.off(".autocompleter").remove();
                }
            });
        }
    };
    /**
     * @method private
     * @name _init
     * @description Initializes plugin
     * @param opts [object] "Initialization options"
     */

    function _init(opts) {
        // Local options
        opts = $.extend({}, options, opts || {}); // Check for Body

        if ($body === null) {
            $body = $("body");
        } // Apply to each element


        var $items = $(this);

        for (var i = 0, count = $items.length; i < count; i++) {
            _build($items.eq(i), opts);
        }

        return $items;
    }
    /**
     * @method private
     * @name _build
     * @description Builds each instance
     * @param $node [jQuery object] "Target jQuery object"
     * @param opts [object] <{}> "Options object"
     */


    function _build($node, opts) {
        if (!$node.hasClass("autocompleter-node")) {
            // Extend options
            opts = $.extend({}, opts, $node.data("autocompleter-options")); // Check for as local or .json file

            if (typeof opts.source === "string" && (opts.source.slice(-5) === ".json" || opts.asLocal === true)) {
                $.ajax({
                    url: opts.source,
                    type: "GET",
                    dataType: "json",
                    async: false
                }).done(function (response) {
                    opts.source = response;
                });
            }

            var html = '<div class="autocompleter ' + opts.customClass.join(" ") + '" id="autocompleter-' + (guid + 1) + '">';

            if (opts.hint) {
                html += '<div class="autocompleter-hint"></div>';
            }

            html += '<ul class="autocompleter-list"></ul>';
            html += "</div>";
            $node.addClass("autocompleter-node").after(html);
            var $autocompleter = $node.next(".autocompleter").eq(0); // Set autocomplete to off for warn overlay

            var originalAutocomplete = $node.attr("autocomplete");
            $node.attr("autocomplete", "off"); // Store plugin data
            //------------


            //------------

            var data = $.extend({
                $node: $node,
                $autocompleter: $autocompleter,
                $selected: null,
                $list: null,
                index: -1,
                hintText: false,
                source: false,
                jqxhr: false,
                response: null,
                focused: false,
                query: "",
                originalAutocomplete: originalAutocomplete,
                guid: guid++
            }, opts); // Bind autocompleter events

            data.$autocompleter.on("mousedown.autocompleter", ".autocompleter-item", data, _select).data("autocompleter", data); // Bind node events

            data.$node.on("keyup.autocompleter", data, _onKeyup).on("keydown.autocompleter", data, _onKeydown).on("focus.autocompleter", data, _onFocus).on("blur.autocompleter", data, _onBlur).on("mousedown.autocompleter", data, _onMousedown);
        }
    }
    /**
     * @method private
     * @name _search
     * @description Local search function, return best collation
     * @param query [string] "Query string"
     * @param source [object] "Source data"
     * @param data [object] "Instance data"
     */


    function _search(query, source, data) {
        var response = [];
        query = query.toUpperCase();

        if (source.length) {
            for (var i = 0; i < 2; i++) {
                for (var item in source) {
                    if (response.length < data.limit) {
                        var label = data.customLabel && source[item][data.customLabel] ? source[item][data.customLabel] : source[item].label;
                        switch (i) {
                            case 0:
                                if (label.toUpperCase().search(query) === 0) {
                                    response.push(source[item]);
                                    delete source[item];
                                }

                                break;

                            case 1:
                                if (label.toUpperCase().search(query) !== -1) {
                                    response.push(source[item]);
                                    delete source[item];
                                }

                                break;
                        }
                    }
                }
            }
        }

        return response;
    }
    /**
     * @method private
     * @name _launch
     * @description Launch autocompleter
     * @param data [object] "Instance data"
     */


    function _launch(data) {
        // Clear previous timeout
        clearTimeout(delayTimeout);
        data.query = $.trim(data.$node.val());

        if (!data.empty && data.query.length === 0 || data.minLength && data.query.length < data.minLength) {
            _clear(data);

            return;
        }

        if (data.delay) {
            // Be careful: delay used also with local source
            delayTimeout = setTimeout(function () {
                _xhr(data);
            }, data.delay);
        } else {
            _xhr(data);
        }
    }
    /**
     * @method private
     * @name _xhr
     * @description Use source locally or create xhr
     * @param data [object] "Instance data"
     */


    function _xhr(data) {
        if (_typeof(data.source) === "object") {
            _clear(data); // Local search


            var search = _search(data.query, _clone(data.source), data);

            if (search.length) {
                _response(search, data);
            }
        } else {
            if (data.jqxhr) {
                data.jqxhr.abort();
            }

            var ajaxData = {
                limit: data.limit
                //,query: data.query
            };
            ajaxData[data.subKey] = data.query;

            if (typeof data.combine === "function") {
                ajaxData = data.combine(ajaxData);
            }
            var cacheKey = ((data.source.indexOf("?")==-1)?data.source.concat('?_'):data.source.concat('&_')).concat(data.query);
            data.jqxhr = $.ajax({
                url: data.source,
                type:data.method,
                dataType: "json",
                crossDomain: true,
                data: ajaxData,
                beforeSend: function beforeSend(xhr) {
                    xhr.setRequestHeader('token',getLocalStorage('TSF_SCM'));
                    data.$autocompleter.addClass("autocompleter-ajax");

                    _clear(data);

                    if (data.cache) {
                        var stored = _getCache(cacheKey, data.cacheExpires);

                        if (stored) {
                            xhr.abort();

                            _response(stored, data);
                        }
                    }
                }
            }).done(function (response) {
                if(typeof data.formatData === "function"){
                    response = data.formatData(response);
                }
                // Get subobject from responce
                if (data.offset) {
                    response = _grab(response, data.offset);
                } // Set cache


                if (data.cache) {
                    _setCache(cacheKey, response);
                }

                _response(response, data);
            }).always(function () {
                //data.$autocompleter.removeClass("autocompleter-ajax");
            });
        }
    }
    /**
     * @method private
     * @name _clear
     * @param data [object] "Instance data"
     */


    function _clear(data) {
        // Clear data
        data.response = null;
        data.$list = null;
        data.$selected = null;
        data.index = 0;
        data.$autocompleter.find(".autocompleter-list").empty();
        data.$autocompleter.find(".autocompleter-hint").removeClass("autocompleter-hint-show").empty();
        data.hintText = false;

        _close(null, data);
    }
    /**
     * @method private
     * @name _response
     * @description Main source response function
     * @param response [object] "Source data"
     * @param data [object] "Instance data"
     */


    function _response(response, data) {
        _buildList(response, data);

        if (data.$autocompleter.hasClass("autocompleter-focus")) {
            _open(null, data);
        }
    }
    /**
     * @method private
     * @name _buildList
     * @description Generate autocompleter-list and update instance data by source
     * @param list [object] "Source data"
     * @param data [object] "Instance data"
     */


    function _buildList(list, data) {
        var menu = "";

        for (var item = 0, count = list.length; item < count; item++) {
            var classes = ["autocompleter-item"],
                highlightReg = new RegExp(data.query, "gi");

            if (data.selectFirst && item === 0 && !data.changeWhenSelect) {
                classes.push("autocompleter-item-selected");
            }

            var label = data.customLabel && list[item][data.customLabel] ? list[item][data.customLabel] : list[item].label,
                clear = label;
            label = data.highlightMatches ? label.replace(highlightReg, "<strong>$&</strong>") : label;
            var value = data.customValue && list[item][data.customValue] ? list[item][data.customValue] : list[item].value; // Apply custom template

            if (data.template) {
                var template = data.template.replace(/({{ label }})/gi, label);

                for (var property in list[item]) {
                    if (Object.prototype.hasOwnProperty.call(list[item], property)) {
                        var regex = new RegExp("{{ " + property + " }}", "gi");
                        template = template.replace(regex, list[item][property]);
                    }
                }

                label = template;
            }

            if (value) {
                menu += '<li data-value="' + value + '" data-label="' + clear + '" class="' + classes.join(" ") + '">' + label + "</li>";
            } else {
                menu += '<li data-label="' + clear + '" class="' + classes.join(" ") + '">' + label + "</li>";
            }
        } // Set hint


        if (list.length && data.hint) {
            var hintLabel = data.customLabel && list[0][data.customLabel] ? list[0][data.customLabel] : list[0].label,
                hint = hintLabel.substr(0, data.query.length).toUpperCase() === data.query.toUpperCase() ? hintLabel : false;

            if (hint && data.query !== hintLabel) {
                var hintReg = new RegExp(data.query, "i");
                var hintText = hint.replace(hintReg, "<span>" + data.query + "</span>");
                data.$autocompleter.find(".autocompleter-hint").addClass("autocompleter-hint-show").html(hintText);
                data.hintText = hintText;
            }
        } // Update data


        data.response = list;
        data.$autocompleter.find(".autocompleter-list").html(menu);
        data.$selected = data.$autocompleter.find(".autocompleter-item-selected").length ? data.$autocompleter.find(".autocompleter-item-selected") : null;
        data.$list = list.length ? data.$autocompleter.find(".autocompleter-item") : null;
        data.index = data.$selected ? data.$list.index(data.$selected) : -1;
        data.$autocompleter.find(".autocompleter-item").each(function (i, j) {
            $(j).data(data.response[i]);
        });
    }
    /**
     * @method private
     * @name _onKeyup
     * @description Keyup events in node, up/down autocompleter-list navigation, typing and enter button callbacks
     * @param e [object] "Event data"
     */


    function _onKeyup(e) {
        var data = e.data,
            code = e.keyCode ? e.keyCode : e.which;

        if ((code === 40 || code === 38) && data.$autocompleter.hasClass("autocompleter-show")) {
            // Arrows up & down
            var len = data.$list.length,
                next,
                prev;

            if (len) {
                // Determine new index
                if (len > 1) {
                    if (data.index === len - 1) {
                        next = data.changeWhenSelect ? -1 : 0;
                        prev = data.index - 1;
                    } else if (data.index === 0) {
                        next = data.index + 1;
                        prev = data.changeWhenSelect ? -1 : len - 1;
                    } else if (data.index === -1) {
                        next = 0;
                        prev = len - 1;
                    } else {
                        next = data.index + 1;
                        prev = data.index - 1;
                    }
                } else if (data.index === -1) {
                    next = 0;
                    prev = 0;
                } else {
                    prev = -1;
                    next = -1;
                }

                data.index = code === 40 ? next : prev; // Update HTML

                data.$list.removeClass("autocompleter-item-selected");

                if (data.index !== -1) {
                    data.$list.eq(data.index).addClass("autocompleter-item-selected");
                }

                data.$selected = data.$autocompleter.find(".autocompleter-item-selected").length ? data.$autocompleter.find(".autocompleter-item-selected") : null;

                if (data.changeWhenSelect) {
                    _setValue(data);
                }
            }
        } else if ($.inArray(code, ignoredKeyCode) === -1 && $.inArray(code, data.ignoredKeyCode) === -1) {
            // Typing
            _launch(data);
        }
    }
    /**
     * @method private
     * @name _onKeydown
     * @description Keydown events in node, up/down for prevent cursor moving and right arrow for hint
     * @param e [object] "Event data"
     */


    function _onKeydown(e) {
        var data = e.data,
            code = e.keyCode ? e.keyCode : e.which;

        if (code === 40 || code === 38) {
            e.preventDefault();
            e.stopPropagation();
        } else if (code === 39) {
            // Right arrow
            if (data.hint && data.hintText && data.$autocompleter.find(".autocompleter-hint").hasClass("autocompleter-hint-show")) {
                e.preventDefault();
                e.stopPropagation();
                var hintOrigin = data.$autocompleter.find(".autocompleter-item").length ? data.$autocompleter.find(".autocompleter-item").eq(0).attr("data-label") : false;

                if (hintOrigin) {
                    data.query = hintOrigin;

                    _setHint(data);
                }
            }
        } else if (code === 13) {
            // Enter
            if (data.$autocompleter.hasClass("autocompleter-show") && data.$selected) {
                _select(e);
            }
        }
    }
    /**
     * @method private
     * @name _onFocus
     * @description Handles instance focus
     * @param e [object] "Event data"
     * @param internal [boolean] "Called by plugin"
     */


    function _onFocus(e, internal) {
        if (!internal) {
            var data = e.data;
            data.$autocompleter.addClass("autocompleter-focus");

            if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass("autocompleter-show")) {
                if (data.focusOpen) {
                    _launch(data);

                    data.focused = true;
                    setTimeout(function () {
                        data.focused = false;
                    }, 500);
                }
            }
        }
    }
    /**
     * @method private
     * @name _onBlur
     * @description Handles instance blur
     * @param e [object] "Event data"
     * @param internal [boolean] "Called by plugin"
     */


    function _onBlur(e, internal) {
        e.preventDefault();
        e.stopPropagation();
        var data = e.data;

        if (!internal) {
            data.$autocompleter.removeClass("autocompleter-focus");

            _close(e);
        }
    }
    /**
     * @method private
     * @name _onMousedown
     * @description Handles mousedown to node
     * @param e [object] "Event data"
     */


    function _onMousedown(e) {
        // Disable middle & right mouse click
        if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) {
            return;
        }

        var data = e.data;

        if (data.$list && !data.focused) {
            if (!data.$node.is(":disabled")) {
                if (isMobile && !isFirefoxMobile) {
                    var el = data.$select[0];

                    if (window.document.createEvent) {
                        // All
                        var evt = window.document.createEvent("MouseEvents");
                        evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                        el.dispatchEvent(evt);
                    } else if (el.fireEvent) {
                        // IE
                        el.fireEvent("onmousedown");
                    }
                } else {
                    // Delegate intent
                    if (data.$autocompleter.hasClass("autocompleter-closed")) {
                        _open(e);
                    } else if (data.$autocompleter.hasClass("autocompleter-show")) {
                        _close(e);
                    }
                }
            }
        }
    }
    /**
     * @method private
     * @name _open
     * @description Opens option set
     * @param e [object] "Event data"
     * @param instanceData [object] "Instance data"
     */


    function _open(e, instanceData) {
        var data = e ? e.data : instanceData;

        if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass("autocompleter-show") && data.$list && data.$list.length) {
            data.$autocompleter.removeClass("autocompleter-closed").addClass("autocompleter-show");
            $body.on("click.autocompleter-" + data.guid, ":not(.autocompleter-item)", data, _closeHelper);
        }
    }
    /**
     * @method private
     * @name _closeHelper
     * @description Determines if event target is outside instance before closing
     * @param e [object] "Event data"
     */


    function _closeHelper(e) {
        if ($(e.target).hasClass("autocompleter-node")) {
            return;
        }

        if ($(e.currentTarget).parents(".autocompleter").length === 0) {
            _close(e);
        }
    }
    /**
     * @method private
     * @name _close
     * @description Closes option set
     * @param e [object] "Event data"
     * @param instanceData [object] "Instance data"
     */


    function _close(e, instanceData) {
        var data = e ? e.data : instanceData;

        if (data.$autocompleter.hasClass("autocompleter-show")) {
            data.$autocompleter.removeClass("autocompleter-show").addClass("autocompleter-closed");
            $body.off(".autocompleter-" + data.guid);
        }
    }
    /**
     * @method private
     * @name _select
     * @description Select item from .autocompleter-list
     * @param e [object] "Event data"
     */


    function _select(e) {
        // Disable middle & right mouse click
        if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) {
            return;
        }

        var data = e.data;
        e.preventDefault();
        e.stopPropagation();

        if (e.type === "mousedown" && $(this).length) {
            data.$selected = $(this);
            data.index = data.$list.index(data.$selected);
        }

        if (!data.$node.prop("disabled")) {
            _close(e);

            _update(data);

            if (e.type === "click") {
                data.$node.trigger("focus", [true]);
            }
        }
    }
    /**
     * @method private
     * @name _setHint
     * @description Set autocompleter by hint
     * @param data [object] "Instance data"
     */


    function _setHint(data) {
        _setValue(data);

        _handleChange(data);

        _launch(data);
    }
    /**
     * @method private
     * @name _setValue
     * @description Set value for native field
     * @param data [object] "Instance data"
     */


    function _setValue(data) {
        if (data.$selected) {
            if (data.hintText && data.$autocompleter.find(".autocompleter-hint").hasClass("autocompleter-hint-show")) {
                data.$autocompleter.find(".autocompleter-hint").removeClass("autocompleter-hint-show");
            }

            data.$node.val(data.$selected.attr("data-value") ? data.$selected.attr("data-value") : data.$selected.attr("data-label"));
        } else {
            if (data.hintText && !data.$autocompleter.find(".autocompleter-hint").hasClass("autocompleter-hint-show")) {
                data.$autocompleter.find(".autocompleter-hint").addClass("autocompleter-hint-show");
            }

            data.$node.val(data.query);
        }
    }
    /**
     * @method private
     * @name _update
     * @param data [object] "Instance data"
     */


    function _update(data) {
        _setValue(data);

        _handleChange(data);

        _clear(data);
    }
    /**
     * @method private
     * @name _handleChange
     * @description Trigger node change event and call the callback function
     * @param data [object] "Instance data"
     */


    function _handleChange(data) {
        data.callback.call(data.$autocompleter, data.$node.val(), data.index, data.response[data.index]);
        data.$node.trigger("change");
    }
    /**
     * @method private
     * @name _grab
     * @description Grab sub-object from object
     * @param response [object] "Native object"
     * @param offset [string] "Offset string"
     */


    function _grab(response, offset) {
        offset = offset.split(".");

        while (response && offset.length) {
            response = response[offset.shift()];
        }

        return response;
    }
    /**
     * @method private
     * @name _setCache
     * @description Store AJAX response in plugin cache
     * @param url [string] "AJAX get query string"
     * @param data [object] "AJAX response data"
     */


    function _setCache(url, data) {
        if (!supportLocalStorage) {
            return;
        }

        if (url && data) {
            cache[url] = {
                value: data,
                timestamp: +new Date()
            }; // Proccess to localStorage

            try {
                localStorage.setItem(localStorageKey, JSON.stringify(cache));
            } catch (e) {
                var code = e.code || e.number || e.message;

                if (code === 22) {
                    _deleteCache();
                } else {
                    throw e;
                }
            }
        }
    }
    /**
     * @method private
     * @name _getCache
     * @description Get cached data by url if exist
     * @param url [string] "AJAX get query string"
     */


    function _getCache(url, expires) {
        var response = false,
            item;
        expires = expires || false;

        if (!url) {
            return false;
        }

        item = cache[url];

        if (item && item.value) {
            response = item.value;

            if (item.timestamp && expires && +new Date() - item.timestamp > expires * 1000) {
                return false;
            } else {
                return response;
            }
        } else {
            return false;
        }
    }
    /**
     * @method private
     * @name _loadCache
     * @description Load all plugin cache from localStorage
     */


    function _loadCache() {
        if (!supportLocalStorage) {
            return;
        }

        return JSON.parse(localStorage.getItem(localStorageKey) || "{}");
    }
    /**
     * @method private
     * @name _deleteCache
     * @description Delete all plugin cache from localStorage
     */


    function _deleteCache() {
        try {
            localStorage.removeItem(localStorageKey);
            cache = _loadCache();
        } catch (e) {//
        }
    }
    /**
     * @method private
     * @name _clone
     * @description Clone JavaScript object
     */


    function _clone(obj) {
        var copy;

        if (null === obj || "object" !== _typeof(obj)) {
            return obj;
        }

        copy = obj.constructor();

        for (var attr in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, attr)) {
                copy[attr] = obj[attr];
            }
        }

        return copy;
    } // Load cache


    var cache = _loadCache();

    $.fn.autocompleter = function (method) {
        if (publics[method]) {
            return publics[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (_typeof(method) === "object" || !method) {
            return _init.apply(this, arguments);
        }

        return this;
    };

    $.autocompleter = function (method) {
        if (method === "defaults") {
            publics.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (method === "clearCache") {
            publics.clearCache.apply(this, null);
        }
    };

})));